// PART OF THE MACHINE SIMULATION. DO NOT CHANGE.

package nachos.machine;

import nachos.security.*;
import nachos.ag.*;

import java.io.File;

/**
 * The master class of the simulated machine. Processes command line arguments,
 * constructs all simulated hardware devices, and starts the grader.
 */
public final class Machine {
	/**
	 * Nachos main entry point.
	 * 
	 * @param args
	 *            the command line arguments.
	 */
	public static void main(final String[] args) {
		System.out.print("nachos 5.0j initializing...");

		Lib.assertTrue(Machine.args == null);
		Machine.args = args;

		processArgs();

		Config.load(configFileName);

		// get the current directory (.)
		baseDirectory = new File(new File("").getAbsolutePath());
		// get the nachos directory (./nachos)
		nachosDirectory = new File(baseDirectory, "nachos");

		String testDirectoryName = Config.getString("FileSystem.testDirectory");

		// get the test directory
		if (testDirectoryName != null) {
			testDirectory = new File(testDirectoryName);
		} else {
			// use ../test
			testDirectory = new File(baseDirectory.getParentFile(), "test");
		}

		securityManager = new NachosSecurityManager(testDirectory);
		privilege = securityManager.getPrivilege();

		privilege.machine = new MachinePrivilege();

		TCB.givePrivilege(privilege);
		privilege.stats = stats;

		securityManager.enable();
		createDevices();
		checkUserClasses();

		autoGrader = (AutoGrader) Lib.constructObject(autoGraderClassName);

		new TCB().start(new Runnable() {
			public void run() {
				autoGrader.start(privilege);
			}
		});
	}

	/**
	 * Yield to non-Nachos threads. Use in non-preemptive JVM's to give
	 * non-Nachos threads a chance to run.
	 */
	public static void yield() {
		Thread.yield();
	}

	/**
	 * Terminate Nachos. Same as <tt>TCB.die()</tt>.
	 */
	public static void terminate() {
		TCB.die();
	}

	/**
	 * Terminate Nachos as the result of an unhandled exception or error.
	 * 
	 * @param e
	 *            the exception or error.
	 */
	public static void terminate(Throwable e) {
		if (e instanceof ThreadDeath)
			throw (ThreadDeath) e;

		e.printStackTrace();
		terminate();
	}

	/**
	 * Print stats, and terminate Nachos.
	 */
	public static void halt() {
		System.out.print("Machine halting!\n\n");
		stats.print();
		terminate();
	}

	/**
	 * Return an array containing all command line arguments.
	 * 
	 * @return the command line arguments passed to Nachos.
	 */
	public static String[] getCommandLineArguments() {
		String[] result = new String[args.length];

		System.arraycopy(args, 0, result, 0, args.length);

		return result;
	}

	private static void processArgs() {
		for (int i = 0; i < args.length;) {
			String arg = args[i++];
			if (arg.length() > 0 && arg.charAt(0) == '-') {
				if (arg.equals("-d")) {
					Lib.assertTrue(i < args.length, "switch without argument");
					Lib.enableDebugFlags(args[i++]);
				} else if (arg.equals("-h")) {
					System.out.print(help);
					System.exit(1);
				} else if (arg.equals("-m")) {
					Lib.assertTrue(i < args.length, "switch without argument");
					try {
						numPhysPages = Integer.parseInt(args[i++]);
					} catch (NumberFormatException e) {
						Lib.assertNotReached("bad value for -m switch");
					}
				} else if (arg.equals("-s")) {
					Lib.assertTrue(i < args.length, "switch without argument");
					try {
						randomSeed = Long.parseLong(args[i++]);
					} catch (NumberFormatException e) {
						Lib.assertNotReached("bad value for -s switch");
					}
				} else if (arg.equals("-x")) {
					Lib.assertTrue(i < args.length, "switch without argument");
					shellProgramName = args[i++];
				} else if (arg.equals("-z")) {
					System.out.print(copyright);
					System.exit(1);
				}
				// these switches are reserved for the autograder
				else if (arg.equals("-[]")) {
					Lib.assertTrue(i < args.length, "switch without argument");
					configFileName = args[i++];
				} else if (arg.equals("--")) {
					Lib.assertTrue(i < args.length, "switch without argument");
					autoGraderClassName = args[i++];
				}
			}
		}

		Lib.seedRandom(randomSeed);
	}

	private static void createDevices() {
		interrupt = new Interrupt(privilege);
		timer = new Timer(privilege);

		if (Config.getBoolean("Machine.bank"))
			bank = new ElevatorBank(privilege);

		if (Config.getBoolean("Machine.processor")) {
			if (numPhysPages == -1)
				numPhysPages = Config.getInteger("Processor.numPhysPages");
			processor = new Processor(privilege, numPhysPages);
		}

		if (Config.getBoolean("Machine.console"))
			console = new StandardConsole(privilege);

		if (Config.getBoolean("Machine.stubFileSystem"))
			stubFileSystem = new StubFileSystem(privilege, testDirectory);

		if (Config.getBoolean("Machine.networkLink"))
			networkLink = new NetworkLink(privilege);
	}

	private static void checkUserClasses() {
		System.out.print(" user-check");

		Class aclsInt = (new int[0]).getClass();
		Class clsObject = Lib.loadClass("java.lang.Object");
		Class clsRunnable = Lib.loadClass("java.lang.Runnable");
		Class clsString = Lib.loadClass("java.lang.String");

		Class clsKernel = Lib.loadClass("nachos.machine.Kernel");
		Class clsFileSystem = Lib.loadClass("nachos.machine.FileSystem");
		Class clsRiderControls = Lib.loadClass("nachos.machine.RiderControls");
		Class clsElevatorControls = Lib
				.loadClass("nachos.machine.ElevatorControls");
		Class clsRiderInterface = Lib
				.loadClass("nachos.machine.RiderInterface");
		Class clsElevatorControllerInterface = Lib
				.loadClass("nachos.machine.ElevatorControllerInterface");

		Class clsAlarm = Lib.loadClass("nachos.threads.Alarm");
		Class clsThreadedKernel = Lib
				.loadClass("nachos.threads.ThreadedKernel");
		Class clsKThread = Lib.loadClass("nachos.threads.KThread");
		Class clsCommunicator = Lib.loadClass("nachos.threads.Communicator");
		Class clsSemaphore = Lib.loadClass("nachos.threads.Semaphore");
		Class clsLock = Lib.loadClass("nachos.threads.Lock");
		Class clsCondition = Lib.loadClass("nachos.threads.Condition");
		Class clsCondition2 = Lib.loadClass("nachos.threads.Condition2");
		Class clsRider = Lib.loadClass("nachos.threads.Rider");
		Class clsElevatorController = Lib
				.loadClass("nachos.threads.ElevatorController");

		Lib.checkDerivation(clsThreadedKernel, clsKernel);

		Lib.checkStaticField(clsThreadedKernel, "alarm", clsAlarm);
		Lib.checkStaticField(clsThreadedKernel, "fileSystem", clsFileSystem);

		Lib.checkMethod(clsAlarm, "waitUntil", new Class[] { long.class },
				void.class);

		Lib.checkConstructor(clsKThread, new Class[] {});
		Lib.checkConstructor(clsKThread, new Class[] { clsRunnable });

		Lib.checkStaticMethod(clsKThread, "currentThread", new Class[] {},
				clsKThread);
		Lib.checkStaticMethod(clsKThread, "finish", new Class[] {}, void.class);
		Lib.checkStaticMethod(clsKThread, "yield", new Class[] {}, void.class);
		Lib.checkStaticMethod(clsKThread, "sleep", new Class[] {}, void.class);

		Lib.checkMethod(clsKThread, "setTarget", new Class[] { clsRunnable },
				clsKThread);
		Lib.checkMethod(clsKThread, "setName", new Class[] { clsString },
				clsKThread);
		Lib.checkMethod(clsKThread, "getName", new Class[] {}, clsString);
		Lib.checkMethod(clsKThread, "fork", new Class[] {}, void.class);
		Lib.checkMethod(clsKThread, "ready", new Class[] {}, void.class);
		Lib.checkMethod(clsKThread, "join", new Class[] {}, void.class);

		Lib.checkField(clsKThread, "schedulingState", clsObject);

		Lib.checkConstructor(clsCommunicator, new Class[] {});
		Lib.checkMethod(clsCommunicator, "speak", new Class[] { int.class },
				void.class);
		Lib.checkMethod(clsCommunicator, "listen", new Class[] {}, int.class);

		Lib.checkConstructor(clsSemaphore, new Class[] { int.class });
		Lib.checkMethod(clsSemaphore, "P", new Class[] {}, void.class);
		Lib.checkMethod(clsSemaphore, "V", new Class[] {}, void.class);

		Lib.checkConstructor(clsLock, new Class[] {});
		Lib.checkMethod(clsLock, "acquire", new Class[] {}, void.class);
		Lib.checkMethod(clsLock, "release", new Class[] {}, void.class);
		Lib.checkMethod(clsLock, "isHeldByCurrentThread", new Class[] {},
				boolean.class);

		Lib.checkConstructor(clsCondition, new Class[] { clsLock });
		Lib.checkConstructor(clsCondition2, new Class[] { clsLock });

		Lib.checkMethod(clsCondition, "sleep", new Class[] {}, void.class);
		Lib.checkMethod(clsCondition, "wake", new Class[] {}, void.class);
		Lib.checkMethod(clsCondition, "wakeAll", new Class[] {}, void.class);
		Lib.checkMethod(clsCondition2, "sleep", new Class[] {}, void.class);
		Lib.checkMethod(clsCondition2, "wake", new Class[] {}, void.class);
		Lib.checkMethod(clsCondition2, "wakeAll", new Class[] {}, void.class);

		Lib.checkDerivation(clsRider, clsRiderInterface);

		Lib.checkConstructor(clsRider, new Class[] {});
		Lib.checkMethod(clsRider, "initialize", new Class[] { clsRiderControls,
				aclsInt }, void.class);

		Lib.checkDerivation(clsElevatorController,
				clsElevatorControllerInterface);

		Lib.checkConstructor(clsElevatorController, new Class[] {});
		Lib.checkMethod(clsElevatorController, "initialize",
				new Class[] { clsElevatorControls }, void.class);
	}

	/**
	 * Prevent instantiation.
	 */
	private Machine() {
	}

	/**
	 * Return the hardware interrupt manager.
	 * 
	 * @return the hardware interrupt manager.
	 */
	public static Interrupt interrupt() {
		return interrupt;
	}

	/**
	 * Return the hardware timer.
	 * 
	 * @return the hardware timer.
	 */
	public static Timer timer() {
		return timer;
	}

	/**
	 * Return the hardware elevator bank.
	 * 
	 * @return the hardware elevator bank, or <tt>null</tt> if it is not
	 *         present.
	 */
	public static ElevatorBank bank() {
		return bank;
	}

	/**
	 * Return the MIPS processor.
	 * 
	 * @return the MIPS processor, or <tt>null</tt> if it is not present.
	 */
	public static Processor processor() {
		return processor;
	}

	/**
	 * Return the hardware console.
	 * 
	 * @return the hardware console, or <tt>null</tt> if it is not present.
	 */
	public static SerialConsole console() {
		return console;
	}

	/**
	 * Return the stub filesystem.
	 * 
	 * @return the stub file system, or <tt>null</tt> if it is not present.
	 */
	public static FileSystem stubFileSystem() {
		return stubFileSystem;
	}

	/**
	 * Return the network link.
	 * 
	 * @return the network link, or <tt>null</tt> if it is not present.
	 */
	public static NetworkLink networkLink() {
		return networkLink;
	}

	/**
	 * Return the autograder.
	 * 
	 * @return the autograder.
	 */
	public static AutoGrader autoGrader() {
		return autoGrader;
	}

	private static Interrupt interrupt = null;
	private static Timer timer = null;
	private static ElevatorBank bank = null;
	private static Processor processor = null;
	private static SerialConsole console = null;
	private static FileSystem stubFileSystem = null;
	private static NetworkLink networkLink = null;
	private static AutoGrader autoGrader = null;

	private static String autoGraderClassName = "nachos.ag.AutoGrader";

	/**
	 * Return the name of the shell program that a user-programming kernel must
	 * run. Make sure <tt>UserKernel.run()</tt> <i>always</i> uses this method
	 * to decide which program to run.
	 * 
	 * @return the name of the shell program to run.
	 */
	public static String getShellProgramName() {
		if (shellProgramName == null)
			shellProgramName = Config.getString("Kernel.shellProgram");

		Lib.assertTrue(shellProgramName != null);
		return shellProgramName;
	}

	private static String shellProgramName = null;

	/**
	 * Return the name of the process class that the kernel should use. In the
	 * multi-programming project, returns <tt>nachos.userprog.UserProcess</tt>.
	 * In the VM project, returns <tt>nachos.vm.VMProcess</tt>. In the
	 * networking project, returns <tt>nachos.network.NetProcess</tt>.
	 * 
	 * @return the name of the process class that the kernel should use.
	 * 
	 * @see nachos.userprog.UserKernel#run
	 * @see nachos.userprog.UserProcess
	 * @see nachos.vm.VMProcess
	 * @see nachos.network.NetProcess
	 */
	public static String getProcessClassName() {
		if (processClassName == null)
			processClassName = Config.getString("Kernel.processClassName");

		Lib.assertTrue(processClassName != null);
		return processClassName;
	}

	private static String processClassName = null;

	private static NachosSecurityManager securityManager;
	private static Privilege privilege;

	private static String[] args = null;

	private static Stats stats = new Stats();

	private static int numPhysPages = -1;
	private static long randomSeed = 0;

	private static File baseDirectory, nachosDirectory, testDirectory;
	private static String configFileName = "nachos.conf";

	private static final String help = "\n"
			+ "Options:\n"
			+ "\n"
			+ "\t-d <debug flags>\n"
			+ "\t\tEnable some debug flags, e.g. -d ti\n"
			+ "\n"
			+ "\t-h\n"
			+ "\t\tPrint this help message.\n"
			+ "\n"
			+ "\t-m <pages>\n"
			+ "\t\tSpecify how many physical pages of memory to simulate.\n"
			+ "\n"
			+ "\t-s <seed>\n"
			+ "\t\tSpecify the seed for the random number generator (seed is a\n"
			+ "\t\tlong).\n" + "\n" + "\t-x <program>\n"
			+ "\t\tSpecify a program that UserKernel.run() should execute,\n"
			+ "\t\tinstead of the value of the configuration variable\n"
			+ "\t\tKernel.shellProgram\n" + "\n" + "\t-z\n"
			+ "\t\tprint the copyright message\n" + "\n"
			+ "\t-- <grader class>\n"
			+ "\t\tSpecify an autograder class to use, instead of\n"
			+ "\t\tnachos.ag.AutoGrader\n" + "\n" + "\t-# <grader arguments>\n"
			+ "\t\tSpecify the argument string to pass to the autograder.\n"
			+ "\n" + "\t-[] <config file>\n"
			+ "\t\tSpecifiy a config file to use, instead of nachos.conf\n"
			+ "";

	private static final String copyright = "\n"
			+ "Copyright 1992-2001 The Regents of the University of California.\n"
			+ "All rights reserved.\n"
			+ "\n"
			+ "Permission to use, copy, modify, and distribute this software and\n"
			+ "its documentation for any purpose, without fee, and without\n"
			+ "written agreement is hereby granted, provided that the above\n"
			+ "copyright notice and the following two paragraphs appear in all\n"
			+ "copies of this software.\n"
			+ "\n"
			+ "IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY\n"
			+ "PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL\n"
			+ "DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS\n"
			+ "DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN\n"
			+ "ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
			+ "\n"
			+ "THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY\n"
			+ "WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"
			+ "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE\n"
			+ "SOFTWARE PROVIDED HEREUNDER IS ON AN \"AS IS\" BASIS, AND THE\n"
			+ "UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE\n"
			+ "MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.\n";

	private static class MachinePrivilege implements Privilege.MachinePrivilege {
		public void setConsole(SerialConsole console) {
			Machine.console = console;
		}
	}
}
